戻り値でのimpl Trait
例
code:rust
fn f() -> impl Iterator<Item = i32> {
}
f は「Iterator<Item = i32> を実装している何か」を返す
具体的な型(Vec<i32>::IntoIter) は外部に漏れない
呼び出し側視点でも、Iterator<Item = i32>しか見えないということ
code:rust
let iter = get_iterator(); // Iterator<Item = i32>
コンパイル時に、iterの 型は具体的な IntoIter<i32> に決まる
impl Iterator のように見えるが、内部では型が決まっている
fn get_value() -> Box<dyn Iterator<Item = i32>> になり、ヒープ確保が発生する
もし、これ↑がgenericsだったら、呼び出し側は具体的な型が見えるはず
genericsは関数の呼び出し時に型を決める
code:rust
fn get_iterator<T: Iterator<Item = i32>>() -> T {
vec!1, 2, 3.into_iter() // ❌ コンパイルエラー }
GPT-4.icon
戻り値の impl Trait は 「存在型(existential type)」 に相当します。
存在型とは?
「ある T が存在するが、具体的な型は公開しない」 という型の概念
impl Trait の戻り値は 「何らかの型が存在するが、それが何かは明かさない」 という性質を持つ
「ある T があるが、それが何かは言わない」 → これは 存在型
ジェネリクス (T: Trait) との違い
ジェネリクス (T) は 「全ての T に対して成り立つ」 (∀)
「任意の T に対して動作する」
fn foo<T: Trait>(x: T) {} は どんな T でも受け付ける
impl Trait は 「ある特定の型 T があるが、それを隠す」 (∃)
「何かの T があるが、それが何かは公開しない」
fn foo() -> impl Trait は ある T を返すが、それが何かは隠す
数学的には:
ジェネリクス (T: Trait) → 「全称型 (∀T)」 (forall T.)
impl Trait (戻り値) → 「存在型 (∃T)」 (exists T.)
このため、戻り値の impl Trait は 存在型の一種 であり、ジェネリクス (T: Trait) とは本質的に異なるもの です。